package com.xiyu.service.service.infra.devops;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xiyu.service.dal.websocket.customerUpgrade.CustomerUpgradeInput;
import com.xiyu.service.dal.websocket.customerUpgrade.CustomerUpgradeMessage;
import com.xiyu.service.enums.common.UserTypeEnum;
import com.xiyu.service.framework.websocket.core.sender.WebSocketMessageSender;
import com.xiyu.service.model.devops.project.DevopsProjectVersion;
import com.xiyu.service.model.devops.server.DevopsServer;
import com.xiyu.service.repository.devops.project.DevopsProjectVersionRepository;
import com.xiyu.service.vo.infra.devops.customer.CustomerGetSingleCustomerInfoInput;
import com.xiyu.service.vo.infra.devops.customer.CustomerGetSingleCustomerInfoOutput;
import com.xiyu.service.vo.infra.devops.customer.CustomerGetServerCustomerInfoOutput;
import com.xiyu.service.vo.infra.devops.customer.CustomerGetServerCustomerInfoInput;
import com.xiyu.service.vo.infra.devops.customer.CustomerSingleGetOutput;
import com.xiyu.service.convert.infra.devops.CustomerConvert;
import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.util.entity.EntityUtils;
import com.xiyu.service.vo.infra.devops.customer.CustomerUpdateInput;
import com.xiyu.service.vo.infra.devops.customer.CustomerCreateInput;
import com.xiyu.service.vo.infra.devops.customer.CustomerPageQueryOutput;
import com.xiyu.service.vo.infra.devops.customer.CustomerPageQueryInput;
import com.xiyu.service.model.devops.customer.DevopsCustomer;
import com.xiyu.service.repository.devops.customer.DevopsCustomerRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.jcraft.jsch.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.babyfish.jimmer.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.io.*;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.xiyu.service.errorCode.infra.devops.CustomerErrorCode.*;
import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;

/**
 * 客户管理 Service 实现类
 */
@Service
@Validated
public class CustomerServiceImpl implements CustomerService {

    @Resource
    private DevopsCustomerRepository devopsCustomerRepository;

    @Resource
    private WebSocketMessageSender webSocketMessageSender;

    @Resource
    private DevopsProjectVersionRepository devopsProjectVersionRepository;

    private static final int CHUNK_SIZE = 8 * 1024 * 1024; // 8MB


    @Override
    public PageResult<CustomerPageQueryOutput> pageQuery(CustomerPageQueryInput inputVO) {
        Page<DevopsCustomer> pageDevopsCustomer = devopsCustomerRepository.pageQuery(inputVO);
        List<CustomerPageQueryOutput> listDevopsCustomer = CustomerConvert.INSTANCE.pageQueryOutputConvert(pageDevopsCustomer.getRows());
        return new PageResult<>(listDevopsCustomer, pageDevopsCustomer.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(CustomerCreateInput inputVO) {
        Optional<DevopsCustomer> duplicateCustomer = devopsCustomerRepository.findByCustomerNo(inputVO.getCustomerNo());
        if(duplicateCustomer.isPresent()){
            throw exception(DEVOPS_CUSTOMER_NO_DUPLICATE);
        }
        DevopsCustomer newDevopsCustomer = CustomerConvert.INSTANCE.createInputConvert(inputVO);
        newDevopsCustomer = devopsCustomerRepository.insert(newDevopsCustomer);
        return newDevopsCustomer.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(CustomerUpdateInput inputVO) {
        Optional<DevopsCustomer> optionalOldDevopsCustomer = devopsCustomerRepository.findById(inputVO.getId());
        if(optionalOldDevopsCustomer.isEmpty()){
            throw exception(DEVOPS_CUSTOMER_NOT_EXIST);
        }
        Optional<DevopsCustomer> duplicateCustomer = devopsCustomerRepository.findByCustomerNo(inputVO.getCustomerNo());
        if(duplicateCustomer.isPresent() && duplicateCustomer.get().id() != inputVO.getId()){
            throw exception(DEVOPS_CUSTOMER_NO_DUPLICATE);
        }
        DevopsCustomer updateDevopsCustomer = CustomerConvert.INSTANCE.updateInputConvert(inputVO);
        if (!EntityUtils.isEquals(optionalOldDevopsCustomer.get(), updateDevopsCustomer))
            devopsCustomerRepository.update(updateDevopsCustomer);
        if(!Objects.equals(optionalOldDevopsCustomer.get().expiresTime(), updateDevopsCustomer.expiresTime()) ||
            !Objects.equals(optionalOldDevopsCustomer.get().onlineAccountCount(), updateDevopsCustomer.onlineAccountCount())){
            System.out.println(sendUpdateInfo(inputVO.getId()));
        }
        return true;
    }

    private String sendUpdateInfo(Long id){
        DevopsCustomer devopsCustomer = devopsCustomerRepository.findDetailById(id).orElse(null);
        if(devopsCustomer == null)
            return "devopsCustomer 为 null";
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode jsonNode = objectMapper.createObjectNode();

        jsonNode.put("accountNo", devopsCustomer.customerNo());
        jsonNode.put("expiresTime", devopsCustomer.expiresTime() == null ? null :  devopsCustomer.expiresTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        jsonNode.put("onlineAccountCount", devopsCustomer.onlineAccountCount());

        String json = null;
        try {
            json = objectMapper.writeValueAsString(jsonNode);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        String url = "";
        if(!devopsCustomer.server().domainName().isEmpty()){
            url = devopsCustomer.server().domainName();
        }else{
            url = url +  "http://" +  devopsCustomer.server().ip();
            if(devopsCustomer.server().port() != null || devopsCustomer.server().port() != 0){
                url = url + ":" + devopsCustomer.server().port();
            }
        }
        url = url + "/" + devopsCustomer.version().project().projectNo() + "-server/admin-api/infra/auth/refresh-account";
        System.out.println("this is url " + url);
        HttpResponse response = HttpRequest.post(url) .body(json)
                .header("Content-Type", "application/json").execute(); // 执行请求
        if (response.isOk()) {
            String body = response.body();
            return "发送成功 " + body;
        }else {
            return "发送post请求失败";
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleted(Long id) {
        Optional<DevopsCustomer> optionalOldDevopsCustomer = devopsCustomerRepository.findById(id);
        if(optionalOldDevopsCustomer.isEmpty()){
            throw exception(DEVOPS_CUSTOMER_NOT_EXIST);
        }
        devopsCustomerRepository.deleteById(id);
        return true;
    }

    @Override
    public CustomerSingleGetOutput singleGet(Long id) {
        Optional<DevopsCustomer> optionDevopsCustomer = devopsCustomerRepository.singleGet(id);
        if(optionDevopsCustomer.isEmpty()){
            throw exception(DEVOPS_CUSTOMER_NOT_EXIST);
        }
        return CustomerConvert.INSTANCE.singleGetOutputConvert(optionDevopsCustomer.get());

    }

    @Override
    public List<CustomerGetServerCustomerInfoOutput> getServerCustomerInfo(HttpServletRequest request, CustomerGetServerCustomerInfoInput inputVO) {
        System.out.println("this is function getServerCustomerInfo post ip " + request.getRemoteAddr());
        List<DevopsCustomer> customers = devopsCustomerRepository.getByIpAndProject(inputVO, request.getRemoteAddr());
        return CustomerConvert.INSTANCE.getServerCustomerInfoOutputConvert(customers);
    }

    @Override
    public CustomerGetSingleCustomerInfoOutput getSingleCustomerInfo(CustomerGetSingleCustomerInfoInput inputVO) {
        DevopsCustomer customer;
        if(inputVO.getCustomerName() == null || inputVO.getCustomerName().isEmpty()){
            customer = getCustomerByUrl(inputVO.getUrl(), inputVO.getProjectNo());
        }else{
            customer = devopsCustomerRepository.findByCustomerNameAndProjectNo(inputVO.getCustomerName(), inputVO.getProjectNo()).orElse(null);
        }
        CustomerGetSingleCustomerInfoOutput output = CustomerConvert.INSTANCE.getSingleCustomerInfoOutputConvert(customer);
        if(!customer.server().domainName().isEmpty())
            output.getServer().setIp(customer.server().domainName());
        return output;
    }

    private DevopsCustomer getCustomerByUrl(String url, String projectNo){
        DevopsCustomer customer;
        url = url.replace("http://", "").replace("https://", "");
        while (true){
            customer = devopsCustomerRepository.findByUrlAndProjectNo(url, projectNo).orElse(null);
            if(customer != null)
                break;
            if(!url.contains("/"))
                break;
            else{
                url = url.substring(0, url.lastIndexOf("/"));
            }
        }
        return customer;
    }

    public void init(Long id){
        DevopsCustomer customer = devopsCustomerRepository.findDetailById(id).orElse(null);
        if(customer == null){
            webSocketSendMessage("customer-init","error", "客户不存在");
            return;
        }
        String messageType = "customer-init";
        String createDatabaseCommand = "mysql -u root -p'" + customer.server().databasePassword() + "' -e \"CREATE DATABASE IF NOT EXISTS " + customer.databaseName() +";\"";
        // 创建JSch实例
        JSch jsch = new JSch();
        Session session;
        try {
            session = jsch.getSession("root", customer.server().ip(), 22);

            session.setPassword(customer.server().serverPassword());

            // 禁用主机密钥检查（仅用于测试环境）
            session.setConfig("StrictHostKeyChecking", "no");

            // 连接到服务器
            session.connect();

            // 打开一个通道并执行命令
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand(createDatabaseCommand);

            channel.connect();
            webSocketSendMessage(messageType,"success", "创建数据库");

            // 等待命令执行完成
            while (!channel.isClosed()) {
                Thread.sleep(100);
            }

            // 输出退出状态
            int exitStatus = channel.getExitStatus();
            if (exitStatus == 0) {
                webSocketSendMessage(messageType,"success", "创建数据库成功");
            } else {
                webSocketSendMessage(messageType,"error", "命令执行失败，退出状态：" + exitStatus);
            }

            // 关闭通道和会话
            channel.disconnect();

            // 创建/work/project目录
            ChannelSftp sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();
            try {
                // 检查远程目录是否存在
                sftpChannel.stat("/work"); // 如果目录存在，stat 不会抛出异常
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    // 如果目录不存在，则创建
                    sftpChannel.mkdir("/work");
                } else {
                    // 如果是其他错误（如权限问题），重新抛出异常
                    e.printStackTrace();
                }
            }
            try {
                // 检查远程目录是否存在
                sftpChannel.stat("/work/project"); // 如果目录存在，stat 不会抛出异常
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    // 如果目录不存在，则创建
                    sftpChannel.mkdir("/work/project");
                } else {
                    // 如果是其他错误（如权限问题），重新抛出异常
                    e.printStackTrace();
                }
            }
            sftpChannel.disconnect();
            String localShFile = Objects.requireNonNull(getClass().getClassLoader().getResource("serverInit/deploy.sh")).toString();
            uploadDirectory(session, localShFile, "/work/project/"+ customer.version().project().projectNo() +"server");
            session.disconnect();

            upgradeCustomer(messageType, customer, customer.version());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行远程命令
     */
    private static void executeCommand(Session session, String command) throws Exception {
        ChannelExec channel = (ChannelExec) session.openChannel("exec");
        channel.setCommand(command);
        channel.connect();

        while (!channel.isClosed()) {
            Thread.sleep(100);
        }

        channel.disconnect();
    }

    /**
     * 上传本地目录到远程服务器
     */
    private static void uploadDirectory(Session session, String localDirPath, String remoteDirPath) throws Exception {
        ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
        channel.connect();

        File localDir = new File(localDirPath);
        if (localDir.isDirectory()) {
            uploadDirectoryRecursive(channel, localDir, remoteDirPath);
        }

        channel.disconnect();
    }

    /**
     * 递归上传目录
     */
    /**
     * 递归上传目录
     */
    private static void uploadDirectoryRecursive(ChannelSftp channel, File localDir, String remoteDirPath) throws Exception {
        try {
            // 检查远程目录是否存在
            channel.stat(remoteDirPath); // 如果目录存在，stat 不会抛出异常
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                // 如果目录不存在，则创建
                channel.mkdir(remoteDirPath);
            } else {
                // 如果是其他错误（如权限问题），重新抛出异常
                throw e;
            }
        }

        // 遍历本地目录的内容
        for (File file : localDir.listFiles()) {
            String remoteFilePath = remoteDirPath + "/" + file.getName();
            if (file.isDirectory()) {
                // 如果是目录，递归处理
                uploadDirectoryRecursive(channel, file, remoteFilePath);
            } else {
                // 如果是文件，上传到远程服务器
                channel.put(new FileInputStream(file), remoteFilePath);
            }
        }
    }


    /**
     * 上传文件并显示进度
     */
    private void uploadFileWithProgress(String messageType, Session session, String localFilePath, String remoteFilePath) throws Exception {
        ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
        channel.connect();

        File localFile = new File(localFilePath);
        long fileSize = localFile.length();

        try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(localFile))) {
            OutputStream os = channel.put(remoteFilePath);

            byte[] buffer = new byte[1024 * 1024];
            int bytesRead;
            long totalBytesRead = 0;

            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
                os.flush();
                totalBytesRead += bytesRead;

                // 显示上传进度
                int progress = (int) ((totalBytesRead * 100) / fileSize);
                webSocketSendMessage(messageType,"success", "\r上传进度: " + progress + "%");
            }

            System.out.println(); // 换行
        }

        channel.disconnect();
    }

    private void upgradeCustomer(String messageType, DevopsCustomer customer, DevopsProjectVersion newVersion){

        String projectNo = newVersion.project().projectNo();

        // SSH连接参数
        String sshHost = customer.server().ip();
        String sshUser = "root";
        String sshPassword = customer.server().serverPassword();

        // 本地文件路径
        String localClientDir = "/work/version/" + projectNo +"/" + newVersion.version() + (customer.isLocal() ? "/dev" : "/front") +"/"+ projectNo +"-client";
//        localClientDir = "d:/work/version/" + projectNo +"/" + newVersion.version() +"/"+ projectNo +"-client";
        String localJarFile = "/work/version/"+ projectNo +"/"+ newVersion.version() +"/" + projectNo +"-server.jar";
//        localJarFile = "d:/work/version/"+ projectNo +"/"+ newVersion.version() +"/" + projectNo +"-server.jar";

        // 远程服务器路径
        String remoteClientDir = "/work/projects/" + projectNo  +"-client";
        String remoteJarFile = "/work/projects/"+ projectNo + "-server/" + projectNo +"-server.jar";

        try {
            // 创建JSch实例并建立SSH连接
            JSch jsch = new JSch();
            Session session = jsch.getSession(sshUser, sshHost, 22);
            session.setPassword(sshPassword);

            // 禁用主机密钥检查（仅用于测试环境）
            session.setConfig("StrictHostKeyChecking", "no");

            session.connect();
            webSocketSendMessage(messageType,"success", "SSH连接成功！");
            System.out.println("SSH连接成功！");

            // 1. 删除远程服务器上的 /work/projects/root-client 文件夹（如果存在）
            executeCommand(session, "rm -rf " + remoteClientDir);
            webSocketSendMessage(messageType,"success", "已删除远程服务器上的 " + localClientDir  +"文件夹。");
            System.out.println("已删除远程服务器上的 /work/projects/" + projectNo  +"-client 文件夹。");

            // 2. 复制本地的 /work/version/projectNo/version/projectNo-client 文件夹到远程服务器
            webSocketSendMessage(messageType,"success", "开始上传前端文件");
            uploadDirectory(session, localClientDir, "/work/projects/" + projectNo  +"-client");
            webSocketSendMessage(messageType,"success", "已将本地的 "+ localClientDir +" 文件夹复制到远程服务器的 /work/projects 目录下。");
            System.out.println("已将本地的 "+ projectNo +"-client 文件夹复制到远程服务器的 /work/projects 目录下。");

            // 3. 删除远程服务器上的 /work/project/project-server/projectNo-server.jar 文件（如果存在）
            executeCommand(session, "rm -f " + remoteJarFile);
            webSocketSendMessage(messageType,"success", "已删除远程服务器上的 /work/project/" + projectNo + "-server/" + projectNo + "-server.jar 文件。");
            System.out.println("已删除远程服务器上的 /work/project/" + projectNo + "-server/" + projectNo + "-server.jar 文件。");

            // 4. 上传本地的 /work/project/project-server/projectNo-server.jar 文件到远程服务器
            webSocketSendMessage(messageType,"success", "开始上传后端文件");
            uploadFileWithProgress(messageType, session, localJarFile, remoteJarFile);

            webSocketSendMessage(messageType,"success", "已将本地的 " + projectNo  +"-server.jar 文件上传到远程服务器的 /work/project/ 目录下。");
            System.out.println("已将本地的 " + projectNo  +"-server.jar 文件上传到远程服务器的 /work/project/ 目录下。");

            execSh(session, projectNo);
            // 关闭SSH连接
            session.disconnect();
            webSocketSendMessage(messageType,"success", "断开SSH连接。");
            System.out.println("断开SSH连接。");

        } catch (Exception e) {
            e.printStackTrace();
            webSocketSendMessage(messageType,"error", e.getMessage());
            throw exception(DEVOPS_CUSTOMER_UPGRADE_ERROR, e.getMessage());
        }
    }

    private void execSh(Session session, String projectNo) throws JSchException, IOException {
        // 打开 Shell 通道
        Channel channel = session.openChannel("shell");
        OutputStream inputToShell = channel.getOutputStream();
        InputStream outputFromShell = channel.getInputStream();

        // 启动通道
        channel.connect();

        // 发送命令
        PrintWriter writer = new PrintWriter(inputToShell, true);
        writer.println("bash /work/project/" + projectNo + "-server/deploy.sh");
        writer.println("exit"); // 退出 Shell


        // 使用 BufferedReader 按行读取输出
        System.out.println("执行结果：");
        BufferedReader reader = new BufferedReader(new InputStreamReader(outputFromShell));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line); // 按行输出
        }



        // 关闭通道和会话
        channel.disconnect();
    }

    @Transactional(rollbackFor = Exception.class)
    public void upgrade(CustomerUpgradeInput input){
        String messageType = "customer-upgrade";
        DevopsCustomer customer = devopsCustomerRepository.findDetailById(input.getId()).orElse(null);
        if(customer == null){
            webSocketSendMessage(messageType,"error", "客户不存在");
            return;
        }

        DevopsProjectVersion newVersion = devopsProjectVersionRepository.findDetailById(input.getVersionId()).orElse(null);
        if(newVersion == null){
            webSocketSendMessage(messageType,"error", "客户版本不存在");
            return;
        }

//        if(customer.versionId().equals(input.getVersionId())){
//            webSocketSendMessage(messageType,"error", "客户已经是此版本，不需要升级");
//            return;
//        }

        if(customer.versionId() > (input.getVersionId())){
            webSocketSendMessage(messageType,"error", "升级不能低于当前版本");
            return;
        }
        upgradeCustomer(messageType, customer, newVersion);

        // 5. 更新版本
        devopsCustomerRepository.updateVersion(input.getId(), input.getVersionId());
        webSocketSendMessage(messageType,"success", "设置客户版本");
    }

    private void webSocketSendMessage(String messageType, String error, String message){
        webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), getLoginUserId(),
                messageType,  new CustomerUpgradeMessage().setStatus(error).setMessage(message));

    }

}
